#region Terms of Usage
/*
 * Copyright 2006 Sameera Perera
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
#endregion
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Collections;

using needle.Services.soab.Data;
using needle.Services.soab.Connectivity;

using System.Threading;
using System.Reflection;
using needle.Properties;

namespace needle.Services.soab.RequestQueue
{
    public class Executor
    {
        protected ServiceAgent svcAgent;

        /// <summary>
        /// Invoked periodically to transfer a <see cref="ServiceRequest"/> from the queue.
        /// </summary>
        /// <remarks>In order to fine-tune the transfer process, override this method
        /// in a derived class.</remarks>
        protected virtual void OnTransferNextRequest()
        {
            ServiceRequest rqst = null;

            // Only one thread should ask for the next transferable request. Other threads 
            // should be blocked until the request's status is set to 'transfered'.
            lock ( this )
            { 
                rqst = QueueManager.Instance.GetNextTransferableRequest();
                if ( rqst != null )
                    rqst.Status = ServiceRequestStatus.Transfered;
            }

            if ( rqst != null )
                Execute( rqst );
        }


        protected virtual void Execute( ServiceRequest rqst )
        {
            ServiceContext serviceContext = rqst.ServiceContext;

            // Get an instance of the proxy class
            object proxy    = serviceContext.GetProxyInstance();

            Type proxyType  = proxy.GetType();
            // Get the method to be executed on the proxy
            Type[] paramTypes     = ( rqst.Payload == null ) ? 
                        new Type[] { typeof( AsyncCompletedEventArgs ) } : 
                        new Type[] { rqst.Payload.GetType(), typeof( AsyncCompletedEventArgs ) };
            MethodInfo remoteMthd = proxyType.GetMethod(
                                            GetRemoteMethodName( rqst.RemoteMethodName ),
                                            paramTypes );

            if ( remoteMthd != null )
            {
                // Attatch event handler
                EventInfo completedEvent = proxyType.GetEvent( GetMethodExecutedEventName( rqst.RemoteMethodName ) );

                // Build the even handler
                Type handlerType = completedEvent.EventHandlerType;
                MethodInfo invk  = handlerType.GetMethod( "Invoke" );
                ParameterInfo[] p= invk.GetParameters();

                MethodInfo mthd  = svcAgent.GetType().GetMethod(
                                        "OnExecuteCompleted",
                                        BindingFlags.Instance | BindingFlags.NonPublic );

                Delegate handler = Delegate.CreateDelegate(
                                        handlerType, svcAgent,
                                        mthd.MakeGenericMethod( p[ 1 ].ParameterType ) );

                completedEvent.AddEventHandler( proxy, handler );

                // Invoke the WebMethod asynchronously using the provided callback as the
                // asyncState.
                object[] paramArray = ( rqst.Payload == null ) ? 
                    new object[] { rqst } : new object[] { rqst.Payload, rqst };

                remoteMthd.Invoke( proxy, paramArray );
            }
        }

        /// <summary>Constructs a new Executor bound to the specified <see cref="ServiceAgent"/>.</summary>
        /// <param name="agent">The <see cref="ServiceAgent"/> to be attached to the Executor.</param>
        public Executor( ServiceAgent agent )
        {
            this.svcAgent = agent;

            // Synchronize with the ConnectionManager's connection state query
            ConnectionManager.Instance.QueriedConnectionState += new EventHandler( ConnectionManager_QueriedConnectionState );
        }

        #region Utility Methods

        private void ConnectionManager_QueriedConnectionState( object sender, EventArgs e )
        {
            if ( ConnectionManager.Instance.IsConnected )
                OnTransferNextRequest();
        }

        #region Proxy Method Management

        /// <summary>Gets the remote method to be invoked.</summary>
        protected string GetRemoteMethodName( string methodName )
        {
            return string.Format(
                        Settings.Default.AsyncMethodNameTemplate,
                        methodName );
        }

        /// <summary>
        /// Gets the name of the event fired when the remote method has returned.
        /// </summary>
        protected string GetMethodExecutedEventName( string methodName )
        {
            return string.Format(
                            Settings.Default.AsyncMethodCompletedNameTemplate,
                            methodName );
        }

        #endregion

        #endregion
    }
}
